using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.PeerResolvers;
using System.Threading;

using Pegasus.Log4Net;
using Pegasus.Runtime.Serialization;

namespace Pegasus.ServiceModel
{
	/// <summary>
	/// This class is still under contstruction as of 4/27/07.
	/// 
	/// This class is used to create a peer to peer network.  This class wraps the WFC NetPeerTcpBinding object to create
	/// a peer mesh that contains any number of nodes and allow broadcast message to be sent to all the nodes in the mesh.
	/// This class also implements a WFC NetTcpBinding for each node, so that private messages can be sent from one node directly
	/// to another.
	/// </summary>
	public partial class PeerNetworkManager : IDisposable
	{
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( PeerNetworkManager ) );

		private string m_meshName = "DefaultMesh";
		private string m_nodeName = string.Empty;
		private object m_nodeTag = null;
		private bool m_ignoreSelf = true;
		private IPAddress m_tcpAddress;
		private int m_tcpPort = 8090;
		
		private Timer m_hartbeatTimer;
		private int m_hartbeatInterval = 5000; // milliseconds.
		private int m_pingInterval = 6000; // milliseconds.
		private Mutex m_pingLock = new Mutex();

		private ServiceHost m_privateHost = null;
		private IBroadcastSender m_broadcastSender = null;

		private NodeData m_nodeInfo = null;
		private Dictionary<string, PeerNodeInfo> m_members = new Dictionary<string, PeerNodeInfo>();

		// Local Const/Readonly values
		private const int DEFAULT_MAX_MESSAGE_SIZE = 65536;
		private const int DEFAULT_MAX_POOL_SIZE = 524288;
		private const int DEFAULT_READ_QUOTA = 2147483647;
		private readonly TimeSpan DEFAULT_TIMEOUT = new TimeSpan( 0, 0, 15 ); // 15 sec
		private readonly TimeSpan DEFAULT_OPEN_CLOSE_TIMEOUT = new TimeSpan( 0, 1, 0 ); // 1 min
		
		/// <summary>
		/// Initializes a new instance of the <see cref="PeerNetworkManager"/> class.
		/// </summary>
		public PeerNetworkManager()
		{
			m_log.DebugFormat( "PeerNetworkManager()" );

			// Set the default values for this node
			// Default Node Names
			m_nodeName = string.Format( "{0}{1}", Environment.MachineName, Process.GetCurrentProcess().Id );

			// Default IP Address
			IPAddress[] addresses = Dns.GetHostAddresses( Environment.MachineName );
			if( addresses.Length > 0 )
			{
				// Use the first IPv4 address we find.
				foreach( IPAddress address in addresses )
				{
					if( address.AddressFamily == AddressFamily.InterNetwork )
					{
						m_tcpAddress = address;
						break;
					}
				}
			}

			// If we have no IPv4 addresses then set the default address to the 
			// loopback device 127.0.0.1
			if( m_tcpAddress == null )
			{
				m_tcpAddress = IPAddress.Loopback;
			}
		}

		/// <summary>
		/// Gets or sets the name of the peer to peer mesh that this node will belong to.
		/// </summary>
		/// <value>The name of the peer mesh.</value>
		public string MeshName
		{
			get
			{
				return m_meshName;
			}

			set
			{
				ThrowIfConnected();
				m_meshName = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the node.
		/// </summary>
		/// <value>The name of the node.</value>
		public string NodeName
		{
			get
			{
				return m_nodeName;
			}

			set
			{
				ThrowIfConnected();
				m_nodeName = value;
			}
		}

		/// <summary>
		/// Gets or sets the node tag object.  This object is transmitted with the node 
		/// information to other nodes in the mesh.
		/// </summary>
		/// <value>The node tag.</value>
		/// <remarks>
		/// The tag object must be a attributed with the data contract and data members.
		/// </remarks>
		public object NodeTag
		{
			get
			{
				return m_nodeTag;
			}
		
			set
			{
				// TODO: Ok this is strange and I don't have time to drill down into MS's code to find out why
				// it doesn't work.  But when the tag is a DataContract object the broadcast messages are not 
				// sent.  There is no error the message is just never received.  If the tag value is null then
				// everything work.  To get around this for now I've made the NodeData.Tag value a string and
				// deserialize the object using the Pegasus XML Serializer and pass a string.
 
				/* Don't look for the DataContractAttribute
				if( value != null )
				{
					bool hasDataContract = false;

					Type type = value.GetType();
					foreach( Attribute attrib in type.GetCustomAttributes( true ) )
					{
						if( attrib.GetType() == typeof( DataContractAttribute ) )
						{
							hasDataContract = true;
							break;
						}
					}

					if( !hasDataContract )
					{
						throw new NotSupportedException( string.Format( "Type {0} must have the data contract attribute to be a node tag object.", type ) );
					}
				}
				*/

				// Look for the Serializable attribute
				if( value != null )
				{
					if( !value.GetType().IsSerializable )
					{
						throw new NotSupportedException( string.Format( "Type {0} must be serializable.", value.GetType() ) );
					}
				}

				ThrowIfConnected();
				m_nodeTag = value;

				// TODO: Update node data
			}
		}

		/// <summary>
		/// Gets or sets the private listen IP address.  By default the private listener will
		/// be bound to the first available IP address.
		/// </summary>
		/// <value>
		///	The private listen IP addresses. Default IP address is the first public IPv4 address.
		/// </value>
		public IPAddress PrivateListenIPAddresses
		{
			get
			{
				return m_tcpAddress;
			}

			set
			{
				ThrowIfConnected();
				m_tcpAddress = value;
			}
		}

		/// <summary>
		/// Gets or sets the private listen port.
		/// </summary>
		/// <value>The private listen port. Default value is port 8090.</value>
		public int PrivateListenPort
		{
			get
			{
				return m_tcpPort;
			}
		
			set
			{
				ThrowIfConnected();
				m_tcpPort = value;
			}
		}

		/// <summary>
		/// Gets or sets the amount of time (in milliseconds) that this node will send the
		/// other nodes a hartbeat, so they know this node is still alive.  Minimal value is 
		/// 100 ms.
		/// </summary>
		/// <value>The hartbeat interval in milliseconds, Default value is 5000 ms (5 sec).</value>
		/// <remarks>
		///	Changing this value will make the mesh more reliable (small number, more hartbeats),
		///	but also incresses the administrative messages to maintain the mesh.
		/// </remarks>
		public int HartbeatInterval
		{
			get
			{
				return m_hartbeatInterval;
			}

			set
			{
				ThrowIfConnected();

				if( value < 100 )
				{
					throw new ArgumentException( "The hardbeat interval must be at least 100 milliseconds" );
				}

				m_hartbeatInterval = value;
			}
		}

		/// <summary>
		/// Gets or sets the timeout interval (in milliseconds) that no message has been received.  If this node has 
		/// not heard any messages (hartbeat, broadcast, private, or administrivate) form a specific node within the 
		/// ping interval then we will this node will ping the node to make sure it is still alive. Minimum value 
		/// is 500 ms.
		/// </summary>
		/// <value>The ping interval in milliseconds, default value is 6000 ms (6 sec).</value>
		public int PingInteralval
		{
			get
			{
				return m_pingInterval;
			}
		
			set
			{
				ThrowIfConnected();

				if( value < 500 )
				{
					throw new ArgumentException( "The ping interval must be at least 500 milliseconds" );
				}

				m_hartbeatInterval = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the node should ignore broadcast messages from it self.
		/// </summary>
		/// <value>
		///	If <c>true</c> the system will filter out any broadcast messages the are from this node; otherwise, <c>false</c>. 
		///	Default value is <c>true</c>.
		/// </value>
		public bool IgnoreMessagesFromSelf
		{
			get
			{
				return m_ignoreSelf;
			}

			set
			{
				ThrowIfConnected();
				m_ignoreSelf = value;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this node is connected to the mesh or not.
		/// </summary>
		/// <value>
		/// If <c>true</c> this node is connected; otherwise, <c>false</c>.
		/// </value>
		public bool IsConnected
		{
			get
			{
				return ( m_broadcastSender != null );
			}
		}

		/// <summary>
		/// Gets a value indicating whether the .NET extenstions for peer networking are installed.
		/// </summary>
		/// <value>
		///	<c>True</c> if this .NET extensions for peer networking are installed; otherwise, <c>false</c>.
		/// </value>
		public static bool IsPeerNetworkingAvailable
		{
			get
			{
				return NetPeerTcpBinding.IsPnrpAvailable;
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			// If we are connected then disconnect
			if( IsConnected )
			{
				Disconnet( false );
			}
		}

		/// <summary>
		/// Connects this node to the peer mesh.
		/// </summary>
		/// <remarks>
		/// <p>This method can take a VERY long time to start if the peer naming services are not started.  You can speed things
		/// up by setting the following services to start automaticly when the system is booted up.</p>
		/// <p>
		///	Net.Tcp Port Sharing Service<br/>
		///	Peer Name Resolution Protocol<br/>
		///	Peer Networking Identity Manager<br/>
		/// </p>
		/// </remarks>
		public void Connect()
		{
			ThrowIfConnected();

			// Setup a new node info object for this node.
			m_nodeInfo = new NodeData();
			m_nodeInfo.Name = m_nodeName;
			m_nodeInfo.MachineName = Environment.MachineName;
			m_nodeInfo.IPAddress = m_tcpAddress;
			m_nodeInfo.Port = m_tcpPort;

			if( m_nodeTag != null )
			{
				m_nodeInfo.Tag = SerializationHelper.ObjectToXml( m_nodeTag );
			}

			ConnectToPrivateNetwork();
			ConnectToPeerNetwork();

			// Add ourself to the members list
			AddNodeToMembers( m_nodeInfo );

			// Send out the enter mesh broadcast message.
			m_broadcastSender.SendNodeEnter( m_nodeInfo );

			// Setup the ping interval events
			m_hartbeatTimer = new Timer( new TimerCallback( OnHartbeatTimer ), null, m_hartbeatInterval, m_hartbeatInterval );

			if( OnConnected != null )
			{
				OnConnected( this, EventArgs.Empty );
			}
		}

		/// <summary>
		/// Connects this node from the group.
		/// </summary>
		public void Disconnet()
		{
			ThrowIfNotConnected();
			Disconnet( false );
		}

		/// <summary>
		/// Gets the names of the member nodes.
		/// </summary>
		/// <returns></returns>
		public NodeInfo[] GetMemberNodes()
		{
			ThrowIfNotConnected();

			NodeInfo[] memberNodes = null;

			lock( m_members )
			{
				int x = 0;
				memberNodes = new NodeInfo[ m_members.Count ];

				foreach( PeerNodeInfo pNodeInfo in m_members.Values )
				{
					memberNodes[ x ] = new NodeInfo( pNodeInfo.Node );
					x++;
				}
			}

			return memberNodes;
		}

		/// <summary>
		/// Sends the message to all nodes in the mesh.
		/// </summary>
		/// <param name="message">The message.</param>
		public void SendBroadcastMessage( object message )
		{
			ThrowIfNotConnected();
			m_broadcastSender.SendBroadcastMessage( m_nodeName, message );
		}

		/// <summary>
		/// Sends the private message.
		/// </summary>
		/// <param name="name">The name of the node that the message should be sent to.</param>
		/// <param name="message">The message.</param>
		public object SendPrivateMessage( string name, object message )
		{
			ThrowIfNotConnected();

			PeerNodeInfo nodeInfo = GetMemberNode( name );
			if( nodeInfo != null )
			{
				// If this message is for ourself then just call the receive private message.
				if( m_nodeName == nodeInfo.Name )
				{
					return ReceivedPrivateMessage( m_nodeName, message );
				}
				else
				{
					return nodeInfo.PrivateSender.SendPrivateMessage( m_nodeName, message );
				}
			}

			throw new CommunicationException( string.Format( "Unable to find node \'{0}\' in the list of nodes", name ) );
		}

		/// <summary>
		/// Received a broadcast message.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		/// <param name="message">The message.</param>
		internal void ReceivedBroadcastMessage( string sender, object message )
		{
			if( OnBroadcastMessageReceived != null )
			{
				OnBroadcastMessageReceived( this, new EventArgsMessage( sender, message ) );
			}

			UpdateSendersTime( sender );
		}

		/// <summary>
		/// Notification that a node has entered the mesh.
		/// </summary>
		/// <param name="nodeData">The node data.</param>
		internal void ReceivedBroadcastNodeEnter( NodeData nodeData )
		{
			// Ignore our own messages.
			if( nodeData.Name == m_nodeName )
			{
				return;
			}

			try
			{
				PeerNodeInfo nodeInfo = AddNodeToMembers( nodeData );
				nodeInfo.PrivateSender.SendWelcome( m_nodeName, m_nodeInfo );
			}
			catch( TimeoutException )
			{
				// Timeout when sending the welcome message we need to remove 
				// the node from our members list.
				RemoveNodeFromMembers( nodeData );
			}
			catch( CommunicationException )
			{
				// Communcation error when sending the welcome message we need 
				// to remove the node from our members list.
				RemoveNodeFromMembers( nodeData );
			}
		}

		/// <summary>
		/// Notification that a node has exited the mesh.
		/// </summary>
		/// <param name="nodeData">The node data.</param>
		internal void ReceivedBroadcastNodeExit( NodeData nodeData )
		{
			// Ignore our own messages.
			if( nodeData.Name == m_nodeName )
			{
				return;
			}

			RemoveNodeFromMembers( nodeData );
		}

		/// <summary>
		/// Receives the node WhoIs message.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="nodeName">Name of the node.</param>
		internal void ReceivedBroadcastNodeWhoIs( string sender, string nodeName )
		{
			// If we don't know who the node is just ignore the request or if the
			// request is from ourself.
			PeerNodeInfo senderNode = GetMemberNode( sender );
			if( senderNode != null && senderNode.Name != m_nodeName )
			{
				UpdateSendersTime( sender );

				try
				{
					senderNode.PrivateSender.SendWhoIsAnswer( m_nodeName, senderNode.Node );
				}
				catch( TimeoutException )
				{
					// If we timed out then just don't respond.
				}
				catch( CommunicationException )
				{
					// Got a communcation/socket excetpion so fault the node
					PrivateSender_Faulted( senderNode.PrivateSender, EventArgs.Empty );
				}
			}
		}

		/// <summary>
		/// Receives the node fault message.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		/// <param name="nodeData">The node data.</param>
		internal void ReceivedBroadcastNodeFault( string sender, NodeData nodeData )
		{
			// if this is us then disconnect from the group
			if( nodeData.Name == m_nodeName )
			{
				// Another node in the group couldn't talk to us so they "faulted" us.
				// That means we get kicked out of the group because we are no longer welcome.
				// Auto reconnect might be a cool feature in this case but there are a lot of 
				// use cases that would have to be covered. For now it's up to the application 
				// as to how to handle this case. 
				Disconnet( true );
			}
			else
			{
				// Just remove the node from our members list
				RemoveNodeFromMembers( nodeData );
			}
		}

		/// <summary>
		/// Receives the hartbeat message from a given node.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		internal void ReceivedBroadcastHartbeat( string sender )
		{
			bool isValidNode = false;
			lock( m_members )
			{
				isValidNode = m_members.ContainsKey( sender );
			}

			if( isValidNode )
			{
				UpdateSendersTime( sender );
			}
			else
			{
				// We got a hartbeat from someone we don't know so 
				// send the WhoIs broadcast message and see if some 
				// else knows how to talk to them.
				if( m_broadcastSender != null )
				{
					m_broadcastSender.SendNodeWhoIs( m_nodeName, sender );
				}
			}
		}

		/// <summary>
		/// Receives a private message.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		/// <param name="message">The message.</param>
		/// <returns></returns>
		internal object ReceivedPrivateMessage( string sender, object message )
		{
			if( OnPrivateMessageReceived != null )
			{
				OnPrivateMessageReceived( this, new EventArgsMessage( sender, message ) );
			}

			// TODO: Single return value???

			UpdateSendersTime( sender );

			return null;
		}

		/// <summary>
		/// Fires the private welcome received.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		/// <param name="nodeData">The node data.</param>
		internal void ReceivedPrivateWelcome( string sender, NodeData nodeData )
		{
			AddNodeToMembers( nodeData );
			UpdateSendersTime( sender );
		}

		/// <summary>
		/// Receiveds the private who is answer.
		/// </summary>
		/// <param name="sender">The name of the node that sent the message.</param>
		/// <param name="nodeData">The node data.</param>
		internal void ReceivedPrivateWhoIsAnswer( string sender, NodeData nodeData )
		{
			// If we don't have the node in our list add it.
			PeerNodeInfo nodeInfo = GetMemberNode( nodeData.Name );
			if( nodeInfo == null )
			{
				AddNodeToMembers( nodeData );
			}
		}

		/// <summary>
		/// Called when at the ping interval to chech to make sure the the other
		/// nodes in the system are still active.
		/// </summary>
		/// <param name="state">The state.</param>
		private void OnHartbeatTimer( object state )
		{
			// If there is no timer (this can happen when things are comming up and down
			// where the timer is cleared on one thread and the timer event has already
			// been dispacted to another thread.
			if( m_hartbeatTimer == null )
			{
				return;
			}
#if DEBUG
			else
			{
				// In debug mode shut the timer off or it will fire again.
				// when you try to step through this method in a debugger.  
				// It will be turned back on at the end of the method.
				m_hartbeatTimer.Change( Timeout.Infinite, Timeout.Infinite );
			}
#endif

			// Send our hartbeat to the reset of the mesh.
			m_broadcastSender.SendHartbeat( m_nodeName );

			// Ping nodes that we have not heard from for a while.
			OnPingOutOfDateNodes();
			
#if DEBUG
			// Just in case it's disconnnected in the middle of a hartbeat.
			if( m_hartbeatTimer != null )
			{
				m_hartbeatTimer.Change( m_hartbeatInterval, m_hartbeatInterval );
			}
#endif
		}

		/// <summary>
		/// Called when to check how long it's been sense we last heard from 
		/// the other nodes and if the interval is greater than the ping 
		/// interval then we will ping the out of date nodes.
		/// </summary>
		private void OnPingOutOfDateNodes()
		{
			// If another thead is pinging then just return
			if( m_pingLock.WaitOne( 0, false ) )
			{
				try
				{
					// Get a list of the member values. Copy the list so 
					// we don't keep it locked for the 
					PeerNodeInfo[] nodeList;
					lock( m_members )
					{
						nodeList = new PeerNodeInfo[ m_members.Count ];
						m_members.Values.CopyTo( nodeList, 0 );
					}

					// If we have not heard from a node in the time span then ping it.
					TimeSpan timeInterval = TimeSpan.FromMilliseconds( m_pingInterval );
					foreach( PeerNodeInfo nodeInfo in nodeList )
					{
						// Don't need to ping ourself
						if( nodeInfo.Name != m_nodeName )
						{
							// If we haven't heard from the node within the ping interval
							// then ping the node.
							if( DateTime.Now - nodeInfo.LastMessageReceived > timeInterval )
							{
								try
								{
									if( nodeInfo.PrivateSender.Ping( m_nodeName ) )
									{
										nodeInfo.LastMessageReceived = DateTime.Now;
									}
									else
									{
										// Call the fault method because the ping failed.
										PrivateSender_Faulted( nodeInfo.PrivateSender, EventArgs.Empty );
									}
								}
								catch
								{
									// The exception will be handled in the fault handler
									// for the private communication interface
								}
							}
						}
					}
				}
				finally
				{
					m_pingLock.ReleaseMutex();
				}
			}
		}

		/// <summary>
		/// This method sets up a listener to listen for private messages.
		/// </summary>
		private void ConnectToPrivateNetwork()
		{
			NetTcpBinding binding = new NetTcpBinding();
			binding.OpenTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.CloseTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.SendTimeout = DEFAULT_TIMEOUT;
			binding.ReceiveTimeout = DEFAULT_TIMEOUT;
			binding.MaxReceivedMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
			binding.MaxBufferPoolSize = DEFAULT_MAX_POOL_SIZE;
			binding.ReaderQuotas.MaxDepth = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxStringContentLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxArrayLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxBytesPerRead = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxNameTableCharCount = DEFAULT_READ_QUOTA;
			binding.Security.Mode = SecurityMode.None;
			binding.PortSharingEnabled = true;

			string privateUrl = string.Format( "net.tcp://{0}:{1}/{2}/{3}", m_tcpAddress.ToString(), m_tcpPort, m_meshName, m_nodeName );

			m_privateHost = new ServiceHost( new PrivateReceiver( this ) );
			m_privateHost.AddServiceEndpoint( typeof( IPrivate ), binding, privateUrl );
			m_privateHost.Open();
		}

		/// <summary>
		/// Connects to peer network.
		/// </summary>
		private void ConnectToPeerNetwork()
		{
			// Setup the receiving singleton object
			InstanceContext instanceContext = new InstanceContext( new BroadcastReceiver( this ) );

			// Setup the peer binding object
			NetPeerTcpBinding binding = new NetPeerTcpBinding();
			binding.Port = 0;
			binding.OpenTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.CloseTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.SendTimeout = DEFAULT_TIMEOUT;
			binding.ReceiveTimeout = DEFAULT_TIMEOUT;
			binding.MaxReceivedMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
			binding.MaxBufferPoolSize = DEFAULT_MAX_POOL_SIZE;
			binding.ReaderQuotas.MaxDepth = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxStringContentLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxArrayLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxBytesPerRead = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxNameTableCharCount = DEFAULT_READ_QUOTA;
			binding.Security.Mode = SecurityMode.None;
			binding.Resolver.Mode = PeerResolverMode.Auto;

			// Setup the peer sender end point 
			ServiceEndpoint endPoint = new ServiceEndpoint( ContractDescription.GetContract( typeof( IBroadcast ) ) );
			endPoint.Address = new EndpointAddress( string.Format( "net.p2p://{0}", m_meshName ) );
			endPoint.Binding = binding;

			// Create the peer channel 
			DuplexChannelFactory<IBroadcastSender> factory = new DuplexChannelFactory<IBroadcastSender>( instanceContext, endPoint );
			m_broadcastSender = factory.CreateChannel();

			// If we are going to ignore our own messages then setup a filter object.
			if( m_ignoreSelf )
			{
				PeerNode peerNode = m_broadcastSender.GetProperty<PeerNode>();
				peerNode.MessagePropagationFilter = new NodeFilterMessagesFromSelf();
			}

			// Open the node and attach to the mesh (this can take a long time if the peer services are not running).
			m_broadcastSender.Open( TimeSpan.FromMinutes( 2.0 ) );
		}

		/// <summary>
		/// Connects to node.
		/// </summary>
		/// <param name="nodeInfo">The node info.</param>
		/// <returns></returns>
		private IPrivateSender ConnectToNode( NodeData nodeInfo )
		{
			NetTcpBinding binding = new NetTcpBinding();
			binding.OpenTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.CloseTimeout = DEFAULT_OPEN_CLOSE_TIMEOUT;
			binding.SendTimeout = DEFAULT_TIMEOUT;
			binding.ReceiveTimeout = DEFAULT_TIMEOUT;
			binding.MaxReceivedMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
			binding.MaxBufferPoolSize = DEFAULT_MAX_POOL_SIZE;
			binding.ReaderQuotas.MaxDepth = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxStringContentLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxArrayLength = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxBytesPerRead = DEFAULT_READ_QUOTA;
			binding.ReaderQuotas.MaxNameTableCharCount = DEFAULT_READ_QUOTA;
			binding.Security.Mode = SecurityMode.None;
			binding.PortSharingEnabled = true;

			string privateUrl = string.Format( "net.tcp://{0}:{1}/{2}/{3}", nodeInfo.IPAddress.ToString(), nodeInfo.Port, m_meshName, nodeInfo.Name );
			EndpointAddress endpoint = new EndpointAddress( privateUrl );

			IPrivateSender privateSender = ChannelFactory<IPrivateSender>.CreateChannel( binding, endpoint );
			privateSender.Faulted += new EventHandler( PrivateSender_Faulted );

			return privateSender;
		}

		/// <summary>
		/// Disconnects this node from the group.
		/// </summary>
		/// <param name="faulted">if set to <c>true</c> if the node was disconnected because of a fault.</param>
		private void Disconnet( bool faulted )
		{
			m_log.DebugFormat( "Disconnet( faulted = {0} )", faulted );

			// Turn off the ping timer.
			if( m_hartbeatTimer != null )
			{
				m_log.Debug( "Disconnet: Dispose of the hartbeat timer." );
				m_hartbeatTimer.Dispose();
				m_hartbeatTimer = null;
			}

			// Turn off our private message listener.
			if( m_privateHost != null )
			{
				m_log.Debug( "Disconnet: Close the private message host interface." );
				m_privateHost.Close();
				m_privateHost = null;
			}

			// Send out the exit mesh broadcast message.
			if( m_broadcastSender != null )
			{
				m_log.Debug( "Disconnet: Close the broadcast message interface." );
				m_broadcastSender.SendNodeExit( m_nodeInfo );
				m_broadcastSender.Close();
				m_broadcastSender = null;
			}

			// Clear the node info and disconnect from the remote nodes.
			m_nodeInfo = null;
			lock( m_members )
			{
				foreach( PeerNodeInfo peerNodeInfo in m_members.Values )
				{
					m_log.DebugFormat( "Disconnet: Disconnect private interface from node {0}", peerNodeInfo.Name );
					DisconnetFromNode( peerNodeInfo );
				}

				m_members.Clear();
			}

			// Fire the disconnected event
			if( OnDisconnected != null )
			{
				OnDisconnected( this, new EventArgsDisconnect( faulted ) );
			}
		}

		/// <summary>
		/// Disconnets from node.
		/// </summary>
		/// <param name="nodeInfo">The node info.</param>
		private void DisconnetFromNode( PeerNodeInfo nodeInfo )
		{
			try
			{
				if( nodeInfo.PrivateSender != null )
				{
					nodeInfo.PrivateSender.Close();
				}
			}
			catch( CommunicationException )
			{
				// if the other side of the connection closed at the same time then
				// we can get an exception, but just ignore them because we are 
				// closing the connection anyway.
			}
			finally
			{
				// Alway clear off the value
				nodeInfo.PrivateSender = null;
			}
		}

		/// <summary>
		/// Adds the node to the members list.
		/// </summary>
		/// <param name="nodeData">The node data.</param>
		/// <returns></returns>
		private PeerNodeInfo AddNodeToMembers( NodeData nodeData )
		{
			// Add the node to our member list
			PeerNodeInfo nodeInfo = new PeerNodeInfo();
			nodeInfo.Name = nodeData.Name;
			nodeInfo.Node = nodeData;
			nodeInfo.LastMessageReceived = DateTime.Now;
			
			// Don't setup a private interface to ourself.
			if( m_nodeName != nodeData.Name )
			{
				nodeInfo.PrivateSender = ConnectToNode( nodeData );
			}

			lock( m_members )
			{
				m_members.Add( nodeInfo.Name, nodeInfo );
			}

			if( OnNodeEnter != null )
			{
				OnNodeEnter( this, new EventArgsNodeInfo( nodeData ) );
			}

			return nodeInfo;
		}

		/// <summary>
		/// Removes the node from members.
		/// </summary>
		/// <param name="nodeData">The node data.</param>
		private void RemoveNodeFromMembers( NodeData nodeData )
		{
			string name = nodeData.Name;

			lock( m_members )
			{
				if( m_members.ContainsKey( name ) )
				{
					DisconnetFromNode( m_members[ name ] );
					m_members.Remove( name );
				}
			}

			if( OnNodeExit != null )
			{
				OnNodeExit( this, new EventArgsNodeInfo( nodeData ) );
			}
		}

		/// <summary>
		/// Handles the Faulted event of the PrivateSender control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void PrivateSender_Faulted( object sender, EventArgs args )
		{
			PeerNodeInfo nodeInfo = null;

			// Find the node that falted and remove it from our members list.
			lock( m_members )
			{
				foreach( PeerNodeInfo node in m_members.Values )
				{
					if( node.PrivateSender != null && node.PrivateSender.Equals( sender ) )
					{
						nodeInfo = node;
						break;
					}
				}
			}

			if( nodeInfo != null )
			{
				RemoveNodeFromMembers( nodeInfo.Node );
				m_broadcastSender.SendNodeFault( m_nodeName, nodeInfo.Node );
			}
		}

		/// <summary>
		/// Gets the member node.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		private PeerNodeInfo GetMemberNode( string name )
		{
			lock( m_members )
			{
				if( m_members.ContainsKey( name ) )
				{
					return m_members[ name ];
				}
			}

			return null;
		}

		/// <summary>
		/// Updates the senders last message received date/time.
		/// </summary>
		/// <param name="sender">The sender.</param>
		private void UpdateSendersTime( string sender )
		{
			PeerNodeInfo node = GetMemberNode( sender );
			if( node != null )
			{
				node.LastMessageReceived = DateTime.Now;
			}
		}


		/// <summary>
		/// Throws if connected.  This is used to prevent changes to connection properties.
		/// </summary>
		private void ThrowIfConnected()
		{
			if( m_broadcastSender != null )
			{
				throw new ArgumentException( "The operation could not be completed because you are connected to the network.  This operation is only valid when disconnected from the network." );
			}
		}

		/// <summary>
		/// Throws if not connected.  This is used when the user trys to send a message but we are not connected to the group.
		/// </summary>
		private void ThrowIfNotConnected()
		{
			if( m_broadcastSender == null )
			{
				throw new ArgumentException( "The operation can not be preformed because you are not connect to the network. This operation is only valid when connected to the network." );
			}
		}

		/// <summary>
		/// Fired when the node connects to the network.
		/// </summary>
		public event EventHandler OnConnected;

		/// <summary>
		/// Fired when the node disconnects from the network.
		/// </summary>
		public event EventHandler<EventArgsDisconnect> OnDisconnected;

		/// <summary>
		/// Fired when a node enters the mesh.
		/// </summary>
		public event EventHandler<EventArgsNodeInfo> OnNodeEnter;

		/// <summary>
		/// Fired when a node exits the mesh
		/// </summary>
		public event EventHandler<EventArgsNodeInfo> OnNodeExit;

		/// <summary>
		/// Fired when a new broadcast message is received.
		/// </summary>
		public event EventHandler<EventArgsMessage> OnBroadcastMessageReceived;

		/// <summary>
		/// Fired wehn a new private message is received.
		/// </summary>
		public event EventHandler<EventArgsMessage> OnPrivateMessageReceived;
	}
}
